<?php
/**
 * Project:     drylamp - rapid application development framework for php
 * File:        QueryWrapper.php
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 * @copyright 2009-2010 Ilya St.
 * @author Ilya St. <provided at gmail dot com>
 * @version 1.0
 */

/**
 * This class allows to instantiate an object of a query, expose desired variables of where clause
 * as public properties, request query results in a form of associative array as well as array of 
 * data objects (ActiveRecords) 
 *
 */
class QueryWrapper {
  
  protected $mOrigSQL;
  protected $mSQL;
  protected $mMYSQLi_result;
  protected $mAffectedRows;
  protected $mCount = 0;
  protected $mWhereVars = array();
  protected $mHaveTokens = false;
  
  public function __construct( $pSQL ) {
    $this->mOrigSQL = $pSQL;
    $this->parseTokens($pSQL);
  }

  public function getAssoc() {
    if ($this->mCount == 0) return NULL;
    $rows = array();
    $this->mMYSQLi_result->data_seek(0);
    while ( $row = $this->mMYSQLi_result->fetch_assoc() ) {
            $rows[] = $row;
    }
    return $rows;
  }
  
  public function getExposables() {
    $rows = $this->getAssoc();
    if (is_null($rows)) return NULL;
    $retArr = array();
    foreach($rows as $row) {
    	$retArr[] = new ExposableArray($row);
    }
    return $retArr;
  }
  
  // TODO: optimize - too slow
  public function getDataObjects(/*arbitrary set of params*/) {
    $assoc = $this->getAssoc();
    if (!is_array($assoc)) return NULL;
    $names = func_get_args();
    $num_args = count($names);
    
    $dataObjects = array();

    $fieldNum = 0;
    $this->mMYSQLi_result->data_seek(0);
    foreach($assoc[0] as $key=>$value) {
       $finfo = $this->mMYSQLi_result->fetch_field_direct($fieldNum);
       
       if ( $num_args > 0 && !in_array($finfo->orgtable,$names)) {
         $fieldNum++;
         continue;
       }
       
       if (!isset($dataObjects[$finfo->orgtable])) {
         $obj = $this->makeDataObject($finfo->orgtable);
         $dataObjects[$finfo->orgtable] = $obj->assocArrResToObjArr($assoc, $finfo->orgtable);
       }
       $fieldNum++;
    }
    return $dataObjects;
  }
  
  
  public function getDataObjectsByClass($pClass) {
    $pClass = strtolower($pClass);
    $temp = $this->getDataObjects($pClass);
    return $temp[$pClass];
  }
  
  public function affectedRows() {
    return $this->mAffectedRows;
  }
  
  public function execute() {
    $this->prepareQuery();
    $this->mMYSQLi_result = DBAdapter::getQueryResult($this->mSQL);
    $this->mAffectedRows = DBAdapter::$link->affected_rows;
    $this->mCount = $this->mMYSQLi_result->num_rows;
  }
  
  public function getQuery() {
    $this->prepareQuery();
    return $this->mSQL;
  }
  
  
  protected function prepareQuery() {
    if ( $this->mHaveTokens ) $this->mSQL = $this->insertTokensInQuery($this->mOrigSQL);
      else $this->mSQL = $this->mOrigSQL;  
  }
  
  /*
   * Returns number of rows, returned by a query.
   * @return int
   */
  public function count() {
    return  $this->mCount;
  }
  
  public function destroy() {
    if ( $this->mMYSQLi_result instanceof mysqli_result ) $this->mMYSQLi_result->free();
  }
  
  public function __destruct() {
     $this->destroy(); 
  }
  
  
  protected function makeDataObject($pClassName) {
    $pClassName = strtolower($pClassName);
    if (class_exists($pClassName)) return new $pClassName();
    return new OnFly($pClassName);
  }
  
  protected function parseTokens($pSQL) {
    $matchCount = preg_match_all("/\{:([a-z_]+[0-9a-z_]*):\}/i", $pSQL, $matchedTokens);  
    if ( $matchCount > 0 ) $this->mHaveTokens = true;
    for ( $i = 0 ; $i < $matchCount ; $i++ ) {
      $this->createWhereVar($matchedTokens[1][$i]);
    }
  }
  
  protected function createWhereVar( $pName ) {
    $this->mWhereVars[$pName] = array('value' => NULL );
  }
  
  private function insertTokensInQuery($pSQL) {
    foreach( $this->mWhereVars as $key => $var ) {
    	$value = $this->escapeString($var['value']);
      if (is_null($value)) $pSQL = str_replace("{:".$key.":}", "NULL", $pSQL);
      elseif (is_bool($value)) {
      	if ($value == true) $pSQL = str_replace("{:".$key.":}", 'TRUE', $pSQL);
      	else $pSQL = str_replace("{:".$key.":}", 'FALSE', $pSQL);
      } 
    	elseif (is_numeric($value)) $pSQL = str_replace("{:".$key.":}", $value, $pSQL);
      elseif (is_string($value)) $pSQL = str_replace("{:".$key.":}", "'".$value. "'", $pSQL);
      else throw new QueryWrapperException("Unknown token type");
    }
    return $pSQL;
  }
  
  //TODO: remove
  public function tokenPreprocess($pTokenVal) {
    if ( is_null($pTokenVal) ) return 'NULL';
    if ( is_numeric($pTokenVal) ) return $pTokenVal;
    return "'".$pTokenVal."'";
  }
  
  public function __set($pName, $pValue) {
    if (array_key_exists($pName, $this->mWhereVars)) {
    	$this->mWhereVars[$pName]['value'] = $pValue;
    } 
  }
  
  public function __get($pName) {
    if (array_key_exists($pName, $this->mWhereVars)) return $this->mWhereVars[$pName]['value'];
    else return null;
  }
  
  protected function escapeString($pParam) {
  	if (is_bool($pParam) || is_null($pParam)) return $pParam;
		return DBAdapter::escapeString($pParam);
  } 
}

class QueryWrapperException extends Exception {}